﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Windows;
using System.Text.RegularExpressions;
using System.IO;

using YouDown.Models;
using System.Windows.Threading;
using System.Threading;
using System.Net;

namespace YouDown.ViewModels
{
    public class MainViewModel : ViewModelBase, IDataErrorInfo
    {
        private readonly BackgroundWorker worker;
        private readonly WebClient client;

        private ObservableCollection<Video> videos;
        private ObservableCollection<QualityLevel> qualityLevels;
        private Video selectedVideo;
        private bool working;
        private bool cancelling;
        private QualityLevel selectedQuality;
        private string targetPath;
        private string addressText;
        private int processProgress;
        private int downloadProgress;

        private const string YoutubeMatch = @"youtube.com\/(w\/|watch).+v=.+";

        private DelegateCommand pathCommand;
        private DelegateCommand startCommand;
        private DelegateCommand stopCommand;
        private DelegateCommand addVideoCommand;
        private DelegateCommand removeVideoCommand;
        private DelegateCommand clearVideoCommand;
        private DelegateCommand importVideoCommand;

        public ObservableCollection<Video> Videos
        {
            get
            {
                if (videos == null)
                    videos = new ObservableCollection<Video>();

                return videos; 
            }
        }

        public Video SelectedVideo
        {
            get { return selectedVideo; }
            set { selectedVideo = value; OnPropertyChanged("SelectedVideo"); OnPropertyChanged("RemoveButtonEnabled"); }
        }

        public ObservableCollection<QualityLevel> QualityLevels
        {
            get
            {
                if (qualityLevels == null)
                    qualityLevels = new ObservableCollection<QualityLevel> 
                    { 
                        new QualityLevel { Description = "Low Quality", Extension = ".flv", Format = 6 },
                        new QualityLevel { Description = "High Quality", Extension = ".hq.mp4", Format = 18 },
                        new QualityLevel { Description = "High Definition", Extension = ".hd.mp4", Format = 22 }
                    };

                return qualityLevels;
            }
        }

        public QualityLevel SelectedQuality
        {
            get { return selectedQuality; }
            set { selectedQuality = value; OnPropertyChanged("SelectedQuality"); }
        }

        public int ProcessProgress
        {
            get { return processProgress; }
            set { processProgress = value; OnPropertyChanged("ProcessProgress"); }
        }

        public int DownloadProgress
        {
            get { return downloadProgress; }
            set { downloadProgress = value; OnPropertyChanged("DownloadProgress"); }
        }

        public string TargetPath
        {
            get
            {
                if (targetPath == null)
                {
                    targetPath = Environment.CurrentDirectory;
                }
                return targetPath;
            }
            set { targetPath = value; OnPropertyChanged("TargetPath"); }
        }

        public string AddressText
        {
            get { return addressText; }
            set { addressText = value; OnPropertyChanged("AddressText"); }
        }

        public bool Working
        {
            get { return working; }
            set { working = value; OnPropertyChanged("Working"); OnPropertyChanged("ControlsEnabled"); OnPropertyChanged("GoButtonEnabled");
            OnPropertyChanged("RemoveButtonEnabled"); OnPropertyChanged("ClearButtonEnabled"); OnPropertyChanged("StopButtonEnabled");
            OnPropertyChanged("TitleText");
            }
        }

        public bool ControlsEnabled { get { return !Working; } }

        public bool GoButtonEnabled { get { return Videos.Count > 0 && ControlsEnabled; } }

        public bool StopButtonEnabled { get { return Working && !cancelling; } }

        public bool ClearButtonEnabled { get { return GoButtonEnabled; } }

        public bool RemoveButtonEnabled { get { return SelectedVideo != null && ControlsEnabled; } }

        public string TitleText
        {
            get
            {
                if (Videos.Count(c => c.State == VideoProcessState.Error) > 0)
                    return "Hover over the error icons to see the error message.";

                if (cancelling)
                    return "Cancelling.";

                if (Working)
                    return "Processing and downloading videos.";

                return "Add video URLs and click \"Go\".";
            }
        }

        public ICommand PathCommand
        {
            get
            {
                if (pathCommand == null)
                    pathCommand = new DelegateCommand(ChoosePath);

                return pathCommand;
            }
        }

        public ICommand GoCommand
        {
            get
            {
                if (startCommand == null)
                    startCommand = new DelegateCommand(Start);

                return startCommand;
            }
        }

        public ICommand StopCommand
        {
            get
            {
                if (stopCommand == null)
                    stopCommand = new DelegateCommand(Stop);

                return stopCommand;
            }
        }

        public ICommand AddCommand
        {
            get
            {
                if (addVideoCommand == null)
                    addVideoCommand = new DelegateCommand(Add, () => string.IsNullOrEmpty(Error) && AddressText != null);

                return addVideoCommand;
            }
        }

        public ICommand RemoveCommand
        {
            get
            {
                if (removeVideoCommand == null)
                    removeVideoCommand = new DelegateCommand(Remove);

                return removeVideoCommand;
            }
        }

        public ICommand ClearCommand
        {
            get
            {
                if (clearVideoCommand == null)
                    clearVideoCommand = new DelegateCommand(Clear);

                return clearVideoCommand;
            }
        }

        public ICommand ImportCommand
        {
            get
            {
                if (importVideoCommand == null)
                    importVideoCommand = new DelegateCommand(Import);

                return importVideoCommand;
            }
        }

        public MainViewModel()
        {
            worker = new BackgroundWorker { WorkerSupportsCancellation = true };
            worker.DoWork += DoWork;
            worker.RunWorkerCompleted += StopWork;

            client = new WebClient();
            client.DownloadProgressChanged += ClientDownloadProgressChanged;
            client.DownloadFileCompleted += Download;
        }

        private void DoWork(object sender, DoWorkEventArgs e)
        {
            Working = true;

            for (int i = 0; i < Videos.Count; i++)
            {
                if (worker.CancellationPending) break;

                var vid = Videos[i];

                if (vid.State == VideoProcessState.Unprocessed)
                {
                    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, (ThreadStart)delegate
                    {
                        Videos[i] = Procesor.Process(vid);
                    });
                }

                ProcessProgress = (100 / Videos.Count * (i + 1)) + 1;
            }

            if (worker.CancellationPending) return;

            if (Videos.Count(c => c.State == VideoProcessState.Unprocessed) > 0)
            {
                Download(this, new AsyncCompletedEventArgs(null, false, null));
            }
        }

        private void Download(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                List<Video> toRemove = Videos.Where(c => c.State == VideoProcessState.Processing).ToList();

                foreach (var vid in toRemove)
                {
                    File.Delete(Path.Combine(TargetPath, vid.FileName));
                }
            }

            if (e.UserState != null)
                ((Video)e.UserState).State = VideoProcessState.Done;

            if (e.Cancelled || Videos.Count(c => c.State == VideoProcessState.Unprocessed || c.State == VideoProcessState.Processing) == 0)
            {
                cancelling = false;
                Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, (ThreadStart)delegate
                {
                    Working = false;
                    ProcessProgress = 0;
                    DownloadProgress = 0;
                    OnPropertyChanged("TitleText");
                });

                return;
            }
            
            if (Videos.Count(c => c.State == VideoProcessState.Unprocessed) > 0)
            {
                Video vid = Videos.Where(c => c.State == VideoProcessState.Unprocessed).First();

                vid.Quality = QualityLevels[0];
                int targetFormat = SelectedQuality.Format;
                string baseAddress = String.Format("http://www.youtube.com/get_video?video_id={0}&l={1}&t={2}&fmt=", vid.Id, vid.L, vid.T);

                if (targetFormat == 22)
                {
                    try
                    {
                        WebRequest.Create(baseAddress + "22").GetResponse();
                        vid.Quality = qualityLevels[2];
                    }
                    catch (WebException) { targetFormat = 18; }
                }

                if (targetFormat == 18)
                {
                    try
                    {
                        WebRequest.Create(baseAddress + "18").GetResponse();
                        vid.Quality = qualityLevels[1];
                    }
                    catch (WebException) { targetFormat = 6; }
                }

                if (worker.CancellationPending) return;

                if (targetFormat == 6)
                {
                    try
                    {
                        WebRequest.Create(baseAddress + "6").GetResponse();
                        vid.Quality = qualityLevels[0];
                    }
                    catch (WebException) { targetFormat = 0; }
                }

                if (targetFormat != 0)
                {
                    vid.State = VideoProcessState.Processing;
                    var targetDir = Path.Combine(TargetPath, vid.FileName);

                    if (File.Exists(targetDir))
                    {
                        vid.Error = String.Format("File {0} already exists in the target directory.", vid.FileName);
                        return;
                    }

                    client.DownloadFileAsync(new Uri(baseAddress + targetFormat), targetDir, vid);
                }
            }
        }

        private void ClientDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, (ThreadStart)delegate
            {
                DownloadProgress = e.ProgressPercentage;
            });
        }

        private void StopWork(object sender, RunWorkerCompletedEventArgs e)
        {
            if (Videos.Count(c => c.State == VideoProcessState.Unprocessed || c.State == VideoProcessState.Processing) == 0)
            {
                cancelling = false;
                Working = false;
                ProcessProgress = 0;
            }
            DownloadProgress = 0;
        }

        private void ChoosePath()
        {
            var fbd = new System.Windows.Forms.FolderBrowserDialog
                          {
                              RootFolder = Environment.SpecialFolder.Desktop,
                              ShowNewFolderButton = true
                          };
            if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                if (Directory.Exists(fbd.SelectedPath))
                {
                    TargetPath = fbd.SelectedPath;
                }
            }
        }

        private void Start()
        {
            if (worker != null && !worker.IsBusy)
            {
                List<Video> toRemove = Videos.Where(c => c.State != VideoProcessState.Unprocessed).ToList();
                
                foreach (var vid in toRemove)
                {
                    Videos.Remove(vid);
                }

                worker.RunWorkerAsync();
            }
        }

        private void Stop()
        {
            if (client.IsBusy)
            {
                client.CancelAsync();
                return;
            }

            if (worker != null && !worker.CancellationPending)
            {
                cancelling = true;
                OnPropertyChanged("StopButtonEnabled");
                OnPropertyChanged("TitleText");

                worker.CancelAsync();
            }
        }

        private void Add()
        {
            if (AddressText != null)
            {
                Add(AddressText);
                AddressText = null;
            }
        }

        private void Add(string add)
        {
            if (!add.Contains("http://"))
                add = add.Insert(0, "http://");

            var vid = new Video(new Uri(add));

            if (vid.Address.Scheme != "http")
            {
                vid.Error = "Invalid address" ;
            }

            Videos.Add(vid);
            OnPropertyChanged("ControlsEnabled"); OnPropertyChanged("GoButtonEnabled");
            OnPropertyChanged("RemoveButtonEnabled"); OnPropertyChanged("ClearButtonEnabled");
        }

        private void Remove()
        {
            if (SelectedVideo != null)
            {
                Videos.Remove(SelectedVideo);
                OnPropertyChanged("GoButtonEnabled"); OnPropertyChanged("RemoveButtonEnabled"); OnPropertyChanged("ClearButtonEnabled"); OnPropertyChanged("TitleText");
            }
        }

        private void Clear()
        {
            Videos.Clear();
            OnPropertyChanged("GoButtonEnabled"); OnPropertyChanged("RemoveButtonEnabled"); OnPropertyChanged("ClearButtonEnabled"); OnPropertyChanged("TitleText");
        }

        private void Import()
        {
            var ofd = new System.Windows.Forms.OpenFileDialog
                          {
                              AutoUpgradeEnabled = true,
                              CheckFileExists = true,
                              DefaultExt = "txt"
                          };
            if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                string[] lines = File.ReadAllLines(ofd.FileName);

                foreach (var line in lines)
                {
                    if (Regex.Match(line, YoutubeMatch).Success)
                        Add(line);
                }
            }
        }

        public string Error
        {
            get { return this["AddressText"]; }
        }

        public string this[string columnName]
        {
            get
            {
                string result = null;

                if (columnName == "AddressText")
                {
                    if (AddressText != null)
                    {
                        if (!Regex.Match(AddressText, YoutubeMatch).Success)
                        {
                            result = "Address is not a valid YouTube video link";
                        }
                    }
                }

                return result;
            }
        }
    }
}